-module(bmula_stats).
-include("bmula.inc").
-include_lib("stdlib/include/qlc.hrl").

-behaviour(gen_server).

-export([start_link/0, init/1]).
-export([handle_cast/2, handle_info/2, handle_call/3]).
-export([terminate/2, code_change/3]).

-export([increment/3, stats/1, marks/0]).

-export([webtool/3]).

-define(TABLE, '$stats').
-define(HISTORY, 1000).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    ets:new(?TABLE, [named_table]),
    History = fun(Name, Time) ->
        dets:open_file(Name, []),
        timer:send_interval(Time, {mark, Name}),
        Name
    end,
    HistoryNames = [
        History('$stats 1 second',    1000),
        History('$stats 1 minute',    1000 * 60),
        History('$stats 30 mintutes', 1000 * 60 * 30),
        History('$stats 2 hours',     1000 * 60 * 60 * 2)
    ],
    {ok, HistoryNames}.

handle_call({increment, NS, Key, Value}, _From, State) -> {reply, do_increment(NS, Key, Value), State};
handle_call({stats, NS}, _From, State) -> {reply, do_stats(NS), State};
handle_call({marks}, _From, State) -> {reply, do_marks(State), State}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info({mark, Name}, State) -> do_mark(Name), {noreply, State};
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.

% --------------------
increment(NS, Key, Value) -> gen_server:call(?MODULE, {increment, NS, Key, Value}).
stats(NS) -> gen_server:call(?MODULE, {stats, NS}).
marks() -> gen_server:call(?MODULE, {marks}).

% --------------------
do_increment(NS, Key, Value) ->
    case catch(ets:update_counter(?TABLE, {NS, Key}, Value)) of
        {'EXIT', {badarg, _}} ->
            ets:insert(?TABLE, {{NS, Key}, Value});
        _ -> ok
    end.

do_stats('_') -> ets:tab2list(?TABLE);
do_stats(NS)  -> [{Key, Value} || {{VNS, Key}, Value} <- ets:tab2list(?TABLE), VNS == NS].

do_mark(Name) ->
    Stats = do_stats('_'),
    Keys = lists:sort(lists:flatten(dets:match(Name, {'$1', '_'}))),
    if
        length(Keys) >= ?HISTORY ->
            {ToDelete, _} = lists:split(length(Keys) - ?HISTORY, Keys),
            [dets:delete(Name, K) || K <- ToDelete];
        true -> ok
    end,
    dets:insert(Name, {now(), Stats}).

do_marks(Names) ->
    lists:map(fun(Name) ->
        "$stats " ++ NameL = atom_to_list(Name),
        {
            list_to_binary(NameL),
            lists:keysort(1, lists:flatten(dets:match(Name, '$1')))
        }
    end, Names).

%% webtool for analyzing stats
webtool(left, _, Link) ->
    NameSpaces = lists:foldl(fun({{Stat, _}, _}, NSs) ->
        gb_sets:add(Stat, NSs)
    end, gb_sets:empty(), stats('_')),
    [<<"<ul>">>, lists:map(fun(Ns) ->
        NsL = atom_to_list(Ns),
        [<<"<li>">>, Link([{namespace, NsL}], NsL), <<"</li>">>]
    end, gb_sets:to_list(NameSpaces))];

webtool(right, Qs, _Link) ->
    case lists:keysearch("namespace", 1, Qs) of
        {value, {_, Ns}} ->
            io_lib:format("~p", [stats(list_to_atom(Ns))]);
        _ ->
            "unkown"
    end.

